#!/usr/bin/python


import json
import time
import datetime
import threading
import pytz

from pytz import timezone
from apscheduler.job import Job
from apscheduler.jobstores.memory import MemoryJobStore
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.executors.pool import ThreadPoolExecutor
from apscheduler.executors.pool import ProcessPoolExecutor

import service
import system
import system.core.globals as sys_globals

from system.core.globals import modules_pkg_path
from system.core.base_service import BaseService
from factories import ServiceFactory, APSchedTriggerFactory, ServiceInvoker

"""
Class       : SchedulerApplication
Description : class of scheduler application which handle scheduler jobs
"""

class SchedulerApplication(BaseService):

    """
    scheduler            : instance of apscheduler background scheduler
    """
    scheduler            = None

    """
    job_container        : current jobs container, instance of memory jobstore
    """
    job_container        = None

    """
    job_records          : a container which store scheduler record from database
    """
    job_records          = list()

    """
    job_executor         : a job executor
    """
    job_executor         = None

    """
    executor_types       : job executor type
    """
    executor_types       = ['ThreadPool', 'ProcessPool']

    """
    max_workers          : max job executor instance
    """
    max_worker           = 10

    """
    scheduler_status    : scheduler runninfg status
    """
    scheduler_status    = 'STOP'

    """
    job_container_alias : job container alias name
    """
    job_container_alias = 'SchedulerApplication'

    """
    job_executor_alias  : job executor alias
    """
    job_executor_alias  = 'SchedulerApplication'

    """
    constructor: initialize Scheduler Application class
    """
    def __init__(self, executor_type = None, max_worker = None):
        """
        initalize scheduler, jobstore and executor
        """
        super(SchedulerApplication, self).__init__()
        self.load_model("scheduler.Scheduler")
        self.scheduler     = BackgroundScheduler()
        self.job_container = MemoryJobStore()
        self.job_executor  = self._create_executor_instance(executor_type, max_worker)
        self.scheduler.add_jobstore(self.job_container, alias = self.job_container_alias)
        self.scheduler.add_executor(self.job_executor,  alias = self.job_executor_alias)
        self._prepare_scheduler_jobs_before_running();
        return

    """
    _prepare_scheduler_jobs_before_running: prepare scheduler jobs before running
    """
    def _prepare_scheduler_jobs_before_running(self):
        """
        Get Scheduler From Database
        """
        scheduler_jobs     = self._query_jobs_from_db()
        scheduler_jobs_len = len(scheduler_jobs)
        if scheduler_jobs_len > 0:
            for i in xrange(0, scheduler_jobs_len):
                self._add_job_to_scheduler(scheduler_jobs[i])
        del scheduler_jobs
        return


    """
    refresh_jobs: refresh jobs by comparing old job list and new job records
    """
    def refresh_jobs(self):
        """
        Get Scheduler From Database
        """
        scheduler_jobs = self._query_jobs_from_db()

        """
        GET old scheduler jobs id 
        """
        job_records_ids    = list()
        job_records_len    = len(self.job_records)
        for i in xrange(0, job_records_len):
            job_records_ids.append(self.job_records[i].id)

        """
        GET new scheduler jobs id 
        """
        scheduler_jobs_ids = list()
        scheduler_jobs_len = len(scheduler_jobs)
        for i in xrange(0, scheduler_jobs_len):
            scheduler_jobs_ids.append(scheduler_jobs[i].id)

        """
        Get increasement, decresement & same job id
        """
        increase_job_ids = list(set(scheduler_jobs_ids) - set(job_records_ids))
        decrease_job_ids = list(set(job_records_ids) - set(scheduler_jobs_ids))
        same_job_ids     = list(set(job_records_ids) & set(scheduler_jobs_ids))

        if len(increase_job_ids):
            for i in xrange(0, scheduler_jobs_len):
                if scheduler_jobs[i].id in increase_job_ids:
                    self._add_job_to_scheduler(scheduler_jobs[i])

        if len(decrease_job_ids):
            print ("run remove job records")
            decrease_job_records = list()
            for job_record in self.job_records:
                if job_record.id in decrease_job_ids:
                    decrease_job_records.append(job_record)
            for i in xrange(0, len(decrease_job_records)):
                self._remove_job_from_scheduler(decrease_job_records[i])
            del decrease_job_records

        if len(same_job_ids):
            for i in xrange(0, scheduler_jobs_len):
                if scheduler_jobs[i].id in same_job_ids:
                    self._modify_job_to_scheduler(scheduler_jobs[i])
        del scheduler_jobs
        return


    """
    start_scheduler: start scheduler
    """
    def start_scheduler(self):
        if self.scheduler is not None:
            self.scheduler.start()
            self.scheduler_status = 'START'
        return

    """
    shoutdown_scheduler: shutdown scheduler
    """
    def shutdown_scheduler(self):
        if self.scheduler is not None:
            self.scheduler.shutdown(wait = True)
            self.scheduler_status = 'STOP'
        return


    """
    _create_executor_instance: create executor for scheduler
    """
    def _create_executor_instance(self, executor_type, max_worker):
        """
        Get Class Object
        """
        class_obj = None
        if executor_type not in self.executor_types:
            class_obj = ThreadPoolExecutor
        else:
            if executor_type == 'ThreadPool':
                class_obj = ThreadPoolExecutor
            elif executor_type == 'ProcessPool':
                class_obj = ProcessPoolExecutor

        if max_worker != None:
           self.max_worker = max_worker
        return class_obj(self.max_worker)
        


    """
    _query_jobs_from_db: query jobs from database
    """
    def _query_jobs_from_db(self):
        schedulers = self.scheduler_model.fetch_all_schedulers()
        return schedulers


    """
    _add_job_to_scheduler: add new jobs to scheduler and update job_records
    """
    def _add_job_to_scheduler(self, job_record):
        """
        Create Arguments and add job into scheduler
        """
        arguments                = dict()
        arguments['id']          = job_record.id
        arguments['task_name']   = job_record.task_name
        arguments['module_name'] = sys_globals.modules_pkg_path + "." + job_record.module_name
        arguments['class_name']  = job_record.class_name
        arguments['arguments']   = ''
        if len(job_record.arguments):
            arguments['arguments'] = json.loads(job_record.arguments)

        delay_execute_time = 10
        timestamp          = time.time() + delay_execute_time;
        tm                 = time.localtime(timestamp)

        job_trigger = APSchedTriggerFactory.create_trigger_instance(job_record.type.lower(),json.loads(job_record.time_val))
        self.scheduler.add_job(func               = "factories:ServiceInvoker.invoke",
                               trigger            = job_trigger,
                               args               = None,
                               kwargs             = arguments,
                               id                 = str(job_record.id),
                               name               = job_record.task_name,
                               misfire_grace_time = delay_execute_time,
                               coalesce           = True,
                               max_instance       = 1,
                               next_run_time      = datetime.datetime(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec),
                               jobstore           = self.job_container_alias,
                               executor           = self.job_executor_alias,
                               replace_existing   = False) 
        self.job_records.append(job_record);
        return


    """
    _modify_job_to_scheduler: update job to scheduler and update job_records
    """
    def _modify_job_to_scheduler(self, job_record):
        job_records_len = len(self.job_records)
        for i in xrange(0, job_records_len):
            if self.job_records[i].id == job_record.id:
                if self.job_records[i].arguments != job_record.arguments or self.job_records[i].time_val != job_record.time_val:
                    self._remove_job_from_scheduler(self.job_records[i])
                    self._add_job_to_scheduler(job_record)
                break
        return


    """
    _remove_job_from_scheduler: remove from scheduler and update job_records
    """
    def _remove_job_from_scheduler(self, job_record):
        #self.scheduler.pause_job(job_record.id, self.job_container_alias)
        self.scheduler.print_jobs(job_store = self.job_container_alias)
        self.scheduler.remove_job(str(job_record.id), self.job_container_alias)
        self.job_records.remove(job_record)
        return

if __name__ == "__main__":
    scheduler_app = SchedulerApplication('ThreadPool', 10)
    scheduler_app.start_scheduler()
    time.sleep(10)
    while True:
        scheduler_app.refresh_jobs()
        time.sleep(5)
    scheduler_app.stop_scheduler()
    #ServiceInvoker.invoke(None, None, "videos2", "service.modules.timer_service.timer_service2", "TimerService2", None)
